/*-------------------------------------------------------------------------
 * drawElements Quality Program OpenGL ES 3.1 Module
 * -------------------------------------------------
 *
 * Copyright 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 *//*!
 * \file
 * \brief Vertex attribute binding tests.
 *//*--------------------------------------------------------------------*/

#include "es31fVertexAttributeBindingTests.hpp"
#include "tcuRenderTarget.hpp"
#include "tcuSurface.hpp"
#include "gluCallLogWrapper.hpp"
#include "gluRenderContext.hpp"
#include "gluPixelTransfer.hpp"
#include "gluShaderProgram.hpp"
#include "gluObjectWrapper.hpp"
#include "gluStrUtil.hpp"
#include "glwFunctions.hpp"
#include "glwEnums.hpp"
#include "deStringUtil.hpp"
#include "deInt32.h"

namespace deqp
{
namespace gles31
{
namespace Functional
{
namespace
{

static const char* const s_colorFragmentShader =		"#version 310 es\n"
														"in mediump vec4 v_color;\n"
														"layout(location = 0) out mediump vec4 fragColor;\n"
														"void main (void)\n"
														"{\n"
														"	fragColor = v_color;\n"
														"}\n";

static const char* const s_positionColorShader =		"#version 310 es\n"
														"in highp vec4 a_position;\n"
														"in highp vec4 a_color;\n"
														"out highp vec4 v_color;\n"
														"void main (void)\n"
														"{\n"
														"	gl_Position = a_position;\n"
														"	v_color = a_color;\n"
														"}\n";

static const char* const s_positionColorOffsetShader =	"#version 310 es\n"
														"in highp vec4 a_position;\n"
														"in highp vec4 a_offset;\n"
														"in highp vec4 a_color;\n"
														"out highp vec4 v_color;\n"
														"void main (void)\n"
														"{\n"
														"	gl_Position = a_position + a_offset;\n"
														"	v_color = a_color;\n"
														"}\n";

// Verifies image contains only yellow or greeen, or a linear combination
// of these colors.
static bool verifyImageYellowGreen (const tcu::Surface& image, tcu::TestLog& log, bool logImageOnSuccess)
{
	using tcu::TestLog;

	const int colorThreshold	= 20;

	tcu::Surface error			(image.getWidth(), image.getHeight());
	bool isOk					= true;

	log << TestLog::Message << "Verifying image contents." << TestLog::EndMessage;

	for (int y = 0; y < image.getHeight(); y++)
	for (int x = 0; x < image.getWidth(); x++)
	{
		const tcu::RGBA pixel = image.getPixel(x, y);
		bool pixelOk = true;

		// Any pixel with !(G ~= 255) is faulty (not a linear combinations of green and yellow)
		if (de::abs(pixel.getGreen() - 255) > colorThreshold)
			pixelOk = false;

		// Any pixel with !(B ~= 0) is faulty (not a linear combinations of green and yellow)
		if (de::abs(pixel.getBlue() - 0) > colorThreshold)
			pixelOk = false;

		error.setPixel(x, y, (pixelOk) ? (tcu::RGBA(0, 255, 0, 255)) : (tcu::RGBA(255, 0, 0, 255)));
		isOk = isOk && pixelOk;
	}

	if (!isOk)
	{
		log << TestLog::Message << "Image verification failed." << TestLog::EndMessage;
		log << TestLog::ImageSet("Verfication result", "Result of rendering")
			<< TestLog::Image("Result",		"Result",		image)
			<< TestLog::Image("ErrorMask",	"Error mask",	error)
			<< TestLog::EndImageSet;
	}
	else
	{
		log << TestLog::Message << "Image verification passed." << TestLog::EndMessage;

		if (logImageOnSuccess)
			log << TestLog::ImageSet("Verfication result", "Result of rendering")
				<< TestLog::Image("Result", "Result", image)
				<< TestLog::EndImageSet;
	}

	return isOk;
}

class BindingRenderCase : public TestCase
{
public:
	enum
	{
		TEST_RENDER_SIZE = 64
	};

						BindingRenderCase	(Context& ctx, const char* name, const char* desc, bool unalignedData);
	virtual				~BindingRenderCase	(void);

	virtual void		init				(void);
	virtual void		deinit				(void);
	IterateResult		iterate				(void);

private:
	virtual void		renderTo			(tcu::Surface& dst) = 0;
	virtual void		createBuffers		(void) = 0;
	virtual void		createShader		(void) = 0;

protected:
	const bool			m_unalignedData;
	glw::GLuint			m_vao;
	glu::ShaderProgram*	m_program;
};

BindingRenderCase::BindingRenderCase (Context& ctx, const char* name, const char* desc, bool unalignedData)
	: TestCase			(ctx, name, desc)
	, m_unalignedData	(unalignedData)
	, m_vao				(0)
	, m_program			(DE_NULL)
{
}

BindingRenderCase::~BindingRenderCase (void)
{
	deinit();
}

void BindingRenderCase::init (void)
{
	// check requirements
	if (m_context.getRenderTarget().getWidth() < TEST_RENDER_SIZE || m_context.getRenderTarget().getHeight() < TEST_RENDER_SIZE)
		throw tcu::NotSupportedError("Test requires at least " + de::toString<int>(TEST_RENDER_SIZE) + "x" + de::toString<int>(TEST_RENDER_SIZE) + " render target");

	// resources
	m_context.getRenderContext().getFunctions().genVertexArrays(1, &m_vao);
	if (m_context.getRenderContext().getFunctions().getError() != GL_NO_ERROR)
		throw tcu::TestError("could not gen vao");

	createBuffers();
	createShader();
}

void BindingRenderCase::deinit (void)
{
	if (m_vao)
	{
		m_context.getRenderContext().getFunctions().deleteVertexArrays(1, &m_vao);
		m_vao = 0;
	}

	delete m_program;
	m_program = DE_NULL;
}

BindingRenderCase::IterateResult BindingRenderCase::iterate (void)
{
	tcu::Surface surface(TEST_RENDER_SIZE, TEST_RENDER_SIZE);

	// draw pattern

	renderTo(surface);

	// verify results

	if (verifyImageYellowGreen(surface, m_testCtx.getLog(), false))
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");
	else if (m_unalignedData)
		m_testCtx.setTestResult(QP_TEST_RESULT_COMPATIBILITY_WARNING, "Failed to draw with unaligned data");
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Image verification failed");

	return STOP;
}

class SingleBindingCase : public BindingRenderCase
{
public:

	enum CaseFlag
	{
		FLAG_ATTRIB_UNALIGNED			= (1<<0),		// !< unalign attributes with relativeOffset
		FLAG_ATTRIB_ALIGNED				= (1<<1),		// !< align attributes with relativeOffset to the buffer begin (and not buffer offset)
		FLAG_ATTRIBS_MULTIPLE_ELEMS		= (1<<2),		// !< use multiple attribute elements
		FLAG_ATTRIBS_SHARED_ELEMS		= (1<<3),		// !< use multiple shared attribute elements. xyzw & rgba stored as (x, y, zr, wg, b, a)

		FLAG_BUF_ALIGNED_OFFSET			= (1<<4),		// !< use aligned offset to the buffer object
		FLAG_BUF_UNALIGNED_OFFSET		= (1<<5),		// !< use unaligned offset to the buffer object
		FLAG_BUF_UNALIGNED_STRIDE		= (1<<6),		// !< unalign buffer elements
	};
						SingleBindingCase	(Context& ctx, const char* name, int flags);
						~SingleBindingCase	(void);

	void				init				(void);
	void				deinit				(void);

private:
	struct TestSpec
	{
		int		bufferOffset;
		int		bufferStride;
		int		positionAttrOffset;
		int		colorAttrOffset;
		bool	hasColorAttr;
	};

	enum
	{
		GRID_SIZE = 20
	};

	void				renderTo			(tcu::Surface& dst);

	static TestSpec		genTestSpec			(int flags);
	static std::string	genTestDescription	(int flags);
	static bool			isDataUnaligned		(int flags);

	void				createBuffers		(void);
	void				createShader		(void);
	std::string			genVertexSource		(void);

	const TestSpec		m_spec;
	glw::GLuint			m_buf;
};

SingleBindingCase::SingleBindingCase (Context& ctx, const char* name, int flags)
	: BindingRenderCase	(ctx, name, genTestDescription(flags).c_str(), isDataUnaligned(flags))
	, m_spec			(genTestSpec(flags))
	, m_buf				(0)
{
	DE_ASSERT(!((flags & FLAG_ATTRIB_UNALIGNED) && (flags & FLAG_ATTRIB_ALIGNED)));
	DE_ASSERT(!((flags & FLAG_ATTRIB_ALIGNED) && (flags & FLAG_BUF_UNALIGNED_STRIDE)));

	DE_ASSERT(!isDataUnaligned(flags));
}

SingleBindingCase::~SingleBindingCase (void)
{
	deinit();
}

void SingleBindingCase::init (void)
{
	// log what we are trying to do

	m_testCtx.getLog()	<< tcu::TestLog::Message
						<< "Rendering " << (int)GRID_SIZE << "x" << (int)GRID_SIZE << " grid.\n"
						<< "Buffer format:\n"
						<< "	bufferOffset: " << m_spec.bufferOffset << "\n"
						<< "	bufferStride: " << m_spec.bufferStride << "\n"
						<< "Vertex position format:\n"
						<< "	type: float4\n"
						<< "	offset: " << m_spec.positionAttrOffset << "\n"
						<< "	total offset: " << m_spec.bufferOffset + m_spec.positionAttrOffset << "\n"
						<< tcu::TestLog::EndMessage;

	if (m_spec.hasColorAttr)
		m_testCtx.getLog()	<< tcu::TestLog::Message
							<< "Color:\n"
							<< "	type: float4\n"
							<< "	offset: " << m_spec.colorAttrOffset << "\n"
							<< "	total offset: " << m_spec.bufferOffset + m_spec.colorAttrOffset << "\n"
							<< tcu::TestLog::EndMessage;
	// init

	BindingRenderCase::init();
}

void SingleBindingCase::deinit (void)
{
	if (m_buf)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buf);
		m_buf = 0;
	}

	BindingRenderCase::deinit();
}

void SingleBindingCase::renderTo (tcu::Surface& dst)
{
	glu::CallLogWrapper gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	const int			positionLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
	const int			colorLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_color");
	const int			colorUniformLoc	= gl.glGetUniformLocation(m_program->getProgram(), "u_color");

	gl.enableLogging(true);

	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	gl.glClear(GL_COLOR_BUFFER_BIT);
	gl.glViewport(0, 0, dst.getWidth(), dst.getHeight());
	gl.glBindVertexArray(m_vao);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set vao");

	gl.glUseProgram(m_program->getProgram());
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");

	if (m_spec.hasColorAttr)
	{
		gl.glBindVertexBuffer(3, m_buf, m_spec.bufferOffset, m_spec.bufferStride);

		gl.glVertexAttribBinding(positionLoc, 3);
		gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, m_spec.positionAttrOffset);
		gl.glEnableVertexAttribArray(positionLoc);

		gl.glVertexAttribBinding(colorLoc, 3);
		gl.glVertexAttribFormat(colorLoc, 4, GL_FLOAT, GL_FALSE, m_spec.colorAttrOffset);
		gl.glEnableVertexAttribArray(colorLoc);

		GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");

		gl.glDrawArrays(GL_TRIANGLES, 0, GRID_SIZE*GRID_SIZE*6);
		GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");
	}
	else
	{
		gl.glBindVertexBuffer(3, m_buf, m_spec.bufferOffset, m_spec.bufferStride);
		gl.glVertexAttribBinding(positionLoc, 3);
		gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, m_spec.positionAttrOffset);
		gl.glEnableVertexAttribArray(positionLoc);

		GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");
		gl.glUniform4f(colorUniformLoc, 0.0f, 1.0f, 0.0f, 1.0f);

		gl.glDrawArrays(GL_TRIANGLES, 0, GRID_SIZE*GRID_SIZE*6);
		GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");
	}

	gl.glFinish();
	gl.glBindVertexArray(0);
	gl.glUseProgram(0);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "clean");

	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}

SingleBindingCase::TestSpec SingleBindingCase::genTestSpec (int flags)
{
	const int	datumSize				= 4;
	const int	bufferOffset			= (flags & FLAG_BUF_ALIGNED_OFFSET) ? (32) : (flags & FLAG_BUF_UNALIGNED_OFFSET) ? (19) : (0);
	const int	attrBufAlignment		= ((bufferOffset % datumSize) == 0) ? (0) : (datumSize - (bufferOffset % datumSize));
	const int	positionAttrOffset		= (flags & FLAG_ATTRIB_UNALIGNED) ? (3) : (flags & FLAG_ATTRIB_ALIGNED) ? (attrBufAlignment) : (0);
	const bool	hasColorAttr			= (flags & FLAG_ATTRIBS_SHARED_ELEMS) || (flags & FLAG_ATTRIBS_MULTIPLE_ELEMS);
	const int	colorAttrOffset			= (flags & FLAG_ATTRIBS_SHARED_ELEMS) ? (2 * datumSize) : (flags & FLAG_ATTRIBS_MULTIPLE_ELEMS) ? (4 * datumSize) : (-1);

	const int	bufferStrideBase		= de::max(positionAttrOffset + 4 * datumSize, colorAttrOffset + 4 * datumSize);
	const int	bufferStrideAlignment	= ((bufferStrideBase % datumSize) == 0) ? (0) : (datumSize - (bufferStrideBase % datumSize));
	const int	bufferStridePadding		= ((flags & FLAG_BUF_UNALIGNED_STRIDE) && deIsAligned32(bufferStrideBase, datumSize)) ? (13) : (!(flags & FLAG_BUF_UNALIGNED_STRIDE) && !deIsAligned32(bufferStrideBase, datumSize)) ? (bufferStrideAlignment) : (0);

	TestSpec spec;

	spec.bufferOffset			= bufferOffset;
	spec.bufferStride			= bufferStrideBase + bufferStridePadding;
	spec.positionAttrOffset		= positionAttrOffset;
	spec.colorAttrOffset		= colorAttrOffset;
	spec.hasColorAttr			= hasColorAttr;

	if (flags & FLAG_ATTRIB_UNALIGNED)
		DE_ASSERT(!deIsAligned32(spec.bufferOffset + spec.positionAttrOffset, datumSize));
	else if (flags & FLAG_ATTRIB_ALIGNED)
		DE_ASSERT(deIsAligned32(spec.bufferOffset + spec.positionAttrOffset, datumSize));

	if (flags & FLAG_BUF_UNALIGNED_STRIDE)
		DE_ASSERT(!deIsAligned32(spec.bufferStride, datumSize));
	else
		DE_ASSERT(deIsAligned32(spec.bufferStride, datumSize));

	return spec;
}

std::string SingleBindingCase::genTestDescription (int flags)
{
	std::ostringstream buf;
	buf << "draw test pattern";

	if (flags & FLAG_ATTRIB_UNALIGNED)
		buf << ", attribute offset (unaligned)";
	if (flags & FLAG_ATTRIB_ALIGNED)
		buf << ", attribute offset (aligned)";

	if (flags & FLAG_ATTRIBS_MULTIPLE_ELEMS)
		buf << ", 2 attributes";
	if (flags & FLAG_ATTRIBS_SHARED_ELEMS)
		buf << ", 2 attributes (some components shared)";

	if (flags & FLAG_BUF_ALIGNED_OFFSET)
		buf << ", buffer offset aligned";
	if (flags & FLAG_BUF_UNALIGNED_OFFSET)
		buf << ", buffer offset unaligned";
	if (flags & FLAG_BUF_UNALIGNED_STRIDE)
		buf << ", buffer stride unaligned";

	return buf.str();
}

bool SingleBindingCase::isDataUnaligned (int flags)
{
	if (flags & FLAG_ATTRIB_UNALIGNED)
		return true;
	if (flags & FLAG_ATTRIB_ALIGNED)
		return false;

	return (flags & FLAG_BUF_UNALIGNED_OFFSET) || (flags & FLAG_BUF_UNALIGNED_STRIDE);
}

void SingleBindingCase::createBuffers (void)
{
	const glw::Functions&	gl		= m_context.getRenderContext().getFunctions();
	std::vector<deUint8>	dataBuf	(m_spec.bufferOffset + m_spec.bufferStride * GRID_SIZE * GRID_SIZE * 6);

	// In interleaved mode color rg and position zw are the same. Select "good" values for r and g
	const tcu::Vec4			colorA	(0.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4			colorB	(0.5f, 1.0f, 0.0f, 1.0f);

	for (int y = 0; y < GRID_SIZE; ++y)
	for (int x = 0; x < GRID_SIZE; ++x)
	{
		const tcu::Vec4&	color = ((x + y) % 2 == 0) ? (colorA) : (colorB);
		const tcu::Vec4		positions[6] =
		{
			tcu::Vec4(float(x+0) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
			tcu::Vec4(float(x+0) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
			tcu::Vec4(float(x+1) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
			tcu::Vec4(float(x+0) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
			tcu::Vec4(float(x+1) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
			tcu::Vec4(float(x+1) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f),
		};

		// copy cell vertices to the buffer.
		for (int v = 0; v < 6; ++v)
			memcpy(&dataBuf[m_spec.bufferOffset + m_spec.positionAttrOffset + m_spec.bufferStride * ((y * GRID_SIZE + x) * 6 + v)], positions[v].getPtr(), sizeof(positions[v]));

		// copy color to buffer
		if (m_spec.hasColorAttr)
			for (int v = 0; v < 6; ++v)
				memcpy(&dataBuf[m_spec.bufferOffset + m_spec.colorAttrOffset + m_spec.bufferStride * ((y * GRID_SIZE + x) * 6 + v)], color.getPtr(), sizeof(color));
	}

	gl.genBuffers(1, &m_buf);
	gl.bindBuffer(GL_ARRAY_BUFFER, m_buf);
	gl.bufferData(GL_ARRAY_BUFFER, (glw::GLsizeiptr)dataBuf.size(), &dataBuf[0], GL_STATIC_DRAW);
	gl.bindBuffer(GL_ARRAY_BUFFER, 0);

	if (gl.getError() != GL_NO_ERROR)
		throw tcu::TestError("could not init buffer");
}

void SingleBindingCase::createShader (void)
{
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(genVertexSource()) << glu::FragmentSource(s_colorFragmentShader));
	m_testCtx.getLog() << *m_program;

	if (!m_program->isOk())
		throw tcu::TestError("could not build shader");
}

std::string SingleBindingCase::genVertexSource (void)
{
	const bool			useUniformColor = !m_spec.hasColorAttr;
	std::ostringstream	buf;

	buf <<	"#version 310 es\n"
			"in highp vec4 a_position;\n";

	if (!useUniformColor)
		buf << "in highp vec4 a_color;\n";
	else
		buf << "uniform highp vec4 u_color;\n";

	buf <<	"out highp vec4 v_color;\n"
			"void main (void)\n"
			"{\n"
			"	gl_Position = a_position;\n"
			"	v_color = " << ((useUniformColor) ? ("u_color") : ("a_color")) << ";\n"
			"}\n";

	return buf.str();
}

class MultipleBindingCase : public BindingRenderCase
{
public:

	enum CaseFlag
	{
		FLAG_ZERO_STRIDE		= (1<<0),	// !< set a buffer stride to zero
		FLAG_INSTANCED			= (1<<1),	// !< set a buffer instance divisor to non-zero
		FLAG_ALIASING_BUFFERS	= (1<<2),	// !< bind buffer to multiple binding points
	};

						MultipleBindingCase		(Context& ctx, const char* name, int flags);
						~MultipleBindingCase	(void);

	void				init					(void);
	void				deinit					(void);

private:
	struct TestSpec
	{
		bool zeroStride;
		bool instanced;
		bool aliasingBuffers;
	};

	enum
	{
		GRID_SIZE = 20
	};

	void				renderTo				(tcu::Surface& dst);

	TestSpec			genTestSpec				(int flags) const;
	std::string			genTestDescription		(int flags) const;
	void				createBuffers			(void);
	void				createShader			(void);

	const TestSpec		m_spec;
	glw::GLuint			m_primitiveBuf;
	glw::GLuint			m_colorOffsetBuf;
};

MultipleBindingCase::MultipleBindingCase (Context& ctx, const char* name, int flags)
	: BindingRenderCase	(ctx, name, genTestDescription(flags).c_str(), false)
	, m_spec			(genTestSpec(flags))
	, m_primitiveBuf	(0)
	, m_colorOffsetBuf	(0)
{
	DE_ASSERT(!(m_spec.instanced && m_spec.zeroStride));
}

MultipleBindingCase::~MultipleBindingCase (void)
{
	deinit();
}

void MultipleBindingCase::init (void)
{
	BindingRenderCase::init();

	// log what we are trying to do

	m_testCtx.getLog()	<< tcu::TestLog::Message
						<< "Rendering " << (int)GRID_SIZE << "x" << (int)GRID_SIZE << " grid.\n"
						<< "Vertex positions:\n"
						<< "	binding point: 1\n"
						<< "Vertex offsets:\n"
						<< "	binding point: 2\n"
						<< "Vertex colors:\n"
						<< "	binding point: 2\n"
						<< "Binding point 1:\n"
						<< "	buffer object: " << m_primitiveBuf << "\n"
						<< "Binding point 2:\n"
						<< "	buffer object: " << ((m_spec.aliasingBuffers) ? (m_primitiveBuf) : (m_colorOffsetBuf)) << "\n"
						<< "	instance divisor: " << ((m_spec.instanced) ? (1) : (0)) << "\n"
						<< "	stride: " << ((m_spec.zeroStride) ? (0) : (4*4*2)) << "\n"
						<< tcu::TestLog::EndMessage;
}

void MultipleBindingCase::deinit (void)
{
	if (m_primitiveBuf)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_primitiveBuf);
		m_primitiveBuf = DE_NULL;
	}

	if (m_colorOffsetBuf)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_colorOffsetBuf);
		m_colorOffsetBuf = DE_NULL;
	}

	BindingRenderCase::deinit();
}

void MultipleBindingCase::renderTo (tcu::Surface& dst)
{
	glu::CallLogWrapper gl					(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	const int			positionLoc			= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
	const int			colorLoc			= gl.glGetAttribLocation(m_program->getProgram(), "a_color");
	const int			offsetLoc			= gl.glGetAttribLocation(m_program->getProgram(), "a_offset");

	const int			positionBinding		= 1;
	const int			colorOffsetBinding	= 2;

	gl.enableLogging(true);

	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	gl.glClear(GL_COLOR_BUFFER_BIT);
	gl.glViewport(0, 0, dst.getWidth(), dst.getHeight());
	gl.glBindVertexArray(m_vao);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set vao");

	gl.glUseProgram(m_program->getProgram());
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");

	// Setup format & binding

	gl.glEnableVertexAttribArray(positionLoc);
	gl.glEnableVertexAttribArray(colorLoc);
	gl.glEnableVertexAttribArray(offsetLoc);

	gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, 0);
	gl.glVertexAttribFormat(colorLoc, 4, GL_FLOAT, GL_FALSE, 0);
	gl.glVertexAttribFormat(offsetLoc, 4, GL_FLOAT, GL_FALSE, sizeof(tcu::Vec4));

	gl.glVertexAttribBinding(positionLoc, positionBinding);
	gl.glVertexAttribBinding(colorLoc, colorOffsetBinding);
	gl.glVertexAttribBinding(offsetLoc, colorOffsetBinding);

	GLU_EXPECT_NO_ERROR(gl.glGetError(), "setup attribs");

	// setup binding points

	gl.glVertexBindingDivisor(positionBinding, 0);
	gl.glBindVertexBuffer(positionBinding, m_primitiveBuf, 0, sizeof(tcu::Vec4));

	{
		const int			stride	= (m_spec.zeroStride) ? (0) : (2 * (int)sizeof(tcu::Vec4));
		const int			offset	= (!m_spec.aliasingBuffers) ? (0) : (m_spec.instanced) ? (6 * (int)sizeof(tcu::Vec4)) : (6 * GRID_SIZE * GRID_SIZE * (int)sizeof(tcu::Vec4));
		const glw::GLuint	buffer	= (m_spec.aliasingBuffers) ? (m_primitiveBuf) : (m_colorOffsetBuf);
		const int			divisor	= (m_spec.instanced) ? (1) : (0);

		gl.glVertexBindingDivisor(colorOffsetBinding, divisor);
		gl.glBindVertexBuffer(colorOffsetBinding, buffer, offset, (glw::GLsizei)stride);
	}

	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set binding points");

	if (m_spec.instanced)
		gl.glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_SIZE*GRID_SIZE);
	else
		gl.glDrawArrays(GL_TRIANGLES, 0, GRID_SIZE*GRID_SIZE*6);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "draw");

	gl.glFinish();
	gl.glBindVertexArray(0);
	gl.glUseProgram(0);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "clean");

	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}

MultipleBindingCase::TestSpec MultipleBindingCase::genTestSpec (int flags) const
{
	MultipleBindingCase::TestSpec spec;

	spec.zeroStride			= !!(flags & FLAG_ZERO_STRIDE);
	spec.instanced			= !!(flags & FLAG_INSTANCED);
	spec.aliasingBuffers	= !!(flags & FLAG_ALIASING_BUFFERS);

	return spec;
}

std::string MultipleBindingCase::genTestDescription (int flags) const
{
	std::ostringstream buf;
	buf << "draw test pattern";

	if (flags & FLAG_ZERO_STRIDE)
		buf << ", zero stride";
	if (flags & FLAG_INSTANCED)
		buf << ", instanced binding point";
	if (flags & FLAG_ALIASING_BUFFERS)
		buf << ", binding points share buffer object";

	return buf.str();
}

void MultipleBindingCase::createBuffers (void)
{
	const glw::Functions&	gl					= m_context.getRenderContext().getFunctions();
	const tcu::Vec4			green				= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4			yellow				= tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);

	const int				vertexDataSize		= (m_spec.instanced) ? (6) : (6 * GRID_SIZE * GRID_SIZE);
	const int				offsetColorSize		= (m_spec.zeroStride) ? (2) : (m_spec.instanced) ? (2 * GRID_SIZE * GRID_SIZE) : (2 * 6 * GRID_SIZE * GRID_SIZE);
	const int				primitiveBufSize	= (m_spec.aliasingBuffers) ? (vertexDataSize + offsetColorSize) : (vertexDataSize);
	const int				colorOffsetBufSize	= (m_spec.aliasingBuffers) ? (0) : (offsetColorSize);

	std::vector<tcu::Vec4>	primitiveData		(primitiveBufSize);
	std::vector<tcu::Vec4>	colorOffsetData		(colorOffsetBufSize);
	tcu::Vec4*				colorOffsetWritePtr = DE_NULL;

	if (m_spec.aliasingBuffers)
	{
		if (m_spec.instanced)
			colorOffsetWritePtr = &primitiveData[6];
		else
			colorOffsetWritePtr = &primitiveData[GRID_SIZE*GRID_SIZE*6];
	}
	else
		colorOffsetWritePtr = &colorOffsetData[0];

	// write vertex position

	if (m_spec.instanced)
	{
		// store single basic primitive
		primitiveData[0] = tcu::Vec4(0.0f,						0.0f,						0.0f, 1.0f);
		primitiveData[1] = tcu::Vec4(0.0f,						2.0f / float(GRID_SIZE),	0.0f, 1.0f);
		primitiveData[2] = tcu::Vec4(2.0f / float(GRID_SIZE),	2.0f / float(GRID_SIZE),	0.0f, 1.0f);
		primitiveData[3] = tcu::Vec4(0.0f,						0.0f,						0.0f, 1.0f);
		primitiveData[4] = tcu::Vec4(2.0f / float(GRID_SIZE),	2.0f / float(GRID_SIZE),	0.0f, 1.0f);
		primitiveData[5] = tcu::Vec4(2.0f / float(GRID_SIZE),	0.0f,						0.0f, 1.0f);
	}
	else
	{
		// store whole grid
		for (int y = 0; y < GRID_SIZE; ++y)
		for (int x = 0; x < GRID_SIZE; ++x)
		{
			primitiveData[(y * GRID_SIZE + x) * 6 + 0] = tcu::Vec4(float(x+0) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
			primitiveData[(y * GRID_SIZE + x) * 6 + 1] = tcu::Vec4(float(x+0) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
			primitiveData[(y * GRID_SIZE + x) * 6 + 2] = tcu::Vec4(float(x+1) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
			primitiveData[(y * GRID_SIZE + x) * 6 + 3] = tcu::Vec4(float(x+0) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
			primitiveData[(y * GRID_SIZE + x) * 6 + 4] = tcu::Vec4(float(x+1) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
			primitiveData[(y * GRID_SIZE + x) * 6 + 5] = tcu::Vec4(float(x+1) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
		}
	}

	// store color&offset

	if (m_spec.zeroStride)
	{
		colorOffsetWritePtr[0] = green;
		colorOffsetWritePtr[1] = tcu::Vec4(0.0f);
	}
	else if (m_spec.instanced)
	{
		for (int y = 0; y < GRID_SIZE; ++y)
		for (int x = 0; x < GRID_SIZE; ++x)
		{
			const tcu::Vec4& color = ((x + y) % 2 == 0) ? (green) : (yellow);

			colorOffsetWritePtr[(y * GRID_SIZE + x) * 2 + 0] = color;
			colorOffsetWritePtr[(y * GRID_SIZE + x) * 2 + 1] = tcu::Vec4(float(x) / float(GRID_SIZE) * 2.0f - 1.0f, float(y) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 0.0f);
		}
	}
	else
	{
		for (int y = 0; y < GRID_SIZE; ++y)
		for (int x = 0; x < GRID_SIZE; ++x)
		for (int v = 0; v < 6; ++v)
		{
			const tcu::Vec4& color = ((x + y) % 2 == 0) ? (green) : (yellow);

			colorOffsetWritePtr[((y * GRID_SIZE + x) * 6 + v) * 2 + 0] = color;
			colorOffsetWritePtr[((y * GRID_SIZE + x) * 6 + v) * 2 + 1] = tcu::Vec4(0.0f);
		}
	}

	// upload vertex data

	gl.genBuffers(1, &m_primitiveBuf);
	gl.bindBuffer(GL_ARRAY_BUFFER, m_primitiveBuf);
	gl.bufferData(GL_ARRAY_BUFFER, (int)(primitiveData.size() * sizeof(tcu::Vec4)), primitiveData[0].getPtr(), GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "upload data");

	if (!m_spec.aliasingBuffers)
	{
		// upload color & offset data

		gl.genBuffers(1, &m_colorOffsetBuf);
		gl.bindBuffer(GL_ARRAY_BUFFER, m_colorOffsetBuf);
		gl.bufferData(GL_ARRAY_BUFFER, (int)(colorOffsetData.size() * sizeof(tcu::Vec4)), colorOffsetData[0].getPtr(), GL_STATIC_DRAW);
		GLU_EXPECT_NO_ERROR(gl.getError(), "upload colordata");
	}
}

void MultipleBindingCase::createShader (void)
{
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_positionColorOffsetShader) << glu::FragmentSource(s_colorFragmentShader));
	m_testCtx.getLog() << *m_program;

	if (!m_program->isOk())
		throw tcu::TestError("could not build shader");
}

class MixedBindingCase : public BindingRenderCase
{
public:

	enum CaseType
	{
		CASE_BASIC = 0,
		CASE_INSTANCED_BINDING,
		CASE_INSTANCED_ATTRIB,

		CASE_LAST
	};

						MixedBindingCase		(Context& ctx, const char* name, const char* desc, CaseType caseType);
						~MixedBindingCase		(void);

	void				init					(void);
	void				deinit					(void);

private:
	enum
	{
		GRID_SIZE = 20
	};

	void				renderTo				(tcu::Surface& dst);
	void				createBuffers			(void);
	void				createShader			(void);

	const CaseType		m_case;
	glw::GLuint			m_posBuffer;
	glw::GLuint			m_colorOffsetBuffer;
};

MixedBindingCase::MixedBindingCase (Context& ctx, const char* name, const char* desc, CaseType caseType)
	: BindingRenderCase		(ctx, name, desc, false)
	, m_case				(caseType)
	, m_posBuffer			(0)
	, m_colorOffsetBuffer	(0)
{
	DE_ASSERT(caseType < CASE_LAST);
}

MixedBindingCase::~MixedBindingCase (void)
{
	deinit();
}

void MixedBindingCase::init (void)
{
	BindingRenderCase::init();
}

void MixedBindingCase::deinit (void)
{
	if (m_posBuffer)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_posBuffer);
		m_posBuffer = DE_NULL;
	}

	if (m_colorOffsetBuffer)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_colorOffsetBuffer);
		m_colorOffsetBuffer = DE_NULL;
	}

	BindingRenderCase::deinit();
}

void MixedBindingCase::renderTo (tcu::Surface& dst)
{
	glu::CallLogWrapper gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	const int			positionLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
	const int			colorLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_color");
	const int			offsetLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_offset");

	gl.enableLogging(true);

	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	gl.glClear(GL_COLOR_BUFFER_BIT);
	gl.glViewport(0, 0, dst.getWidth(), dst.getHeight());
	gl.glBindVertexArray(m_vao);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set vao");

	gl.glUseProgram(m_program->getProgram());
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");

	switch (m_case)
	{
		case CASE_BASIC:
		{
			// bind position using vertex_attrib_binding api

			gl.glBindVertexBuffer(positionLoc, m_posBuffer, 0, (glw::GLsizei)sizeof(tcu::Vec4));
			gl.glVertexAttribBinding(positionLoc, positionLoc);
			gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, 0);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set binding");

			// bind color using old api

			gl.glBindBuffer(GL_ARRAY_BUFFER, m_colorOffsetBuffer);
			gl.glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, glw::GLsizei(2 * sizeof(tcu::Vec4)), DE_NULL);
			gl.glVertexAttribPointer(offsetLoc, 4, GL_FLOAT, GL_FALSE, glw::GLsizei(2 * sizeof(tcu::Vec4)), glu::BufferOffsetAsPointer(sizeof(tcu::Vec4)));
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");

			// draw
			gl.glEnableVertexAttribArray(positionLoc);
			gl.glEnableVertexAttribArray(colorLoc);
			gl.glEnableVertexAttribArray(offsetLoc);
			gl.glDrawArrays(GL_TRIANGLES, 0, 6*GRID_SIZE*GRID_SIZE);
			break;
		}

		case CASE_INSTANCED_BINDING:
		{
			// bind position using old api
			gl.glBindBuffer(GL_ARRAY_BUFFER, m_posBuffer);
			gl.glVertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 0, DE_NULL);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");

			// bind color using vertex_attrib_binding api
			gl.glBindVertexBuffer(colorLoc, m_colorOffsetBuffer, 0, (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
			gl.glVertexBindingDivisor(colorLoc, 1);

			gl.glVertexAttribBinding(colorLoc, colorLoc);
			gl.glVertexAttribBinding(offsetLoc, colorLoc);

			gl.glVertexAttribFormat(colorLoc, 4, GL_FLOAT, GL_FALSE, 0);
			gl.glVertexAttribFormat(offsetLoc, 4, GL_FLOAT, GL_FALSE, sizeof(tcu::Vec4));

			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set binding");

			// draw
			gl.glEnableVertexAttribArray(positionLoc);
			gl.glEnableVertexAttribArray(colorLoc);
			gl.glEnableVertexAttribArray(offsetLoc);
			gl.glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_SIZE*GRID_SIZE);
			break;
		}

		case CASE_INSTANCED_ATTRIB:
		{
			// bind position using vertex_attrib_binding api
			gl.glBindVertexBuffer(positionLoc, m_posBuffer, 0, (glw::GLsizei)sizeof(tcu::Vec4));
			gl.glVertexAttribBinding(positionLoc, positionLoc);
			gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, 0);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set binding");

			// bind color using old api
			gl.glBindBuffer(GL_ARRAY_BUFFER, m_colorOffsetBuffer);
			gl.glVertexAttribPointer(colorLoc, 4, GL_FLOAT, GL_FALSE, glw::GLsizei(2 * sizeof(tcu::Vec4)), DE_NULL);
			gl.glVertexAttribPointer(offsetLoc, 4, GL_FLOAT, GL_FALSE, glw::GLsizei(2 * sizeof(tcu::Vec4)), glu::BufferOffsetAsPointer(sizeof(tcu::Vec4)));
			gl.glVertexAttribDivisor(colorLoc, 1);
			gl.glVertexAttribDivisor(offsetLoc, 1);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "set va");

			// draw
			gl.glEnableVertexAttribArray(positionLoc);
			gl.glEnableVertexAttribArray(colorLoc);
			gl.glEnableVertexAttribArray(offsetLoc);
			gl.glDrawArraysInstanced(GL_TRIANGLES, 0, 6, GRID_SIZE*GRID_SIZE);
			break;
		}

		default:
			DE_ASSERT(DE_FALSE);
	}

	gl.glFinish();
	gl.glBindVertexArray(0);
	gl.glUseProgram(0);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "clean");

	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}

void MixedBindingCase::createBuffers (void)
{
	const glw::Functions&	gl								= m_context.getRenderContext().getFunctions();
	const tcu::Vec4			green							= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4			yellow							= tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);

	// draw grid. In instanced mode, each cell is an instance
	const bool				instanced						= (m_case == CASE_INSTANCED_BINDING) || (m_case == CASE_INSTANCED_ATTRIB);
	const int				numCells						= GRID_SIZE*GRID_SIZE;
	const int				numPositionCells				= (instanced) ? (1) : (numCells);
	const int				numPositionElements				= 6 * numPositionCells;
	const int				numInstanceElementsPerCell		= (instanced) ? (1) : (6);
	const int				numColorOffsetElements			= numInstanceElementsPerCell * numCells;

	std::vector<tcu::Vec4>	positionData					(numPositionElements);
	std::vector<tcu::Vec4>	colorOffsetData					(2 * numColorOffsetElements);

	// positions

	for (int primNdx = 0; primNdx < numPositionCells; ++primNdx)
	{
		positionData[primNdx*6 + 0] =  tcu::Vec4(0.0f,						0.0f,						0.0f, 1.0f);
		positionData[primNdx*6 + 1] =  tcu::Vec4(0.0f,						2.0f / float(GRID_SIZE),	0.0f, 1.0f);
		positionData[primNdx*6 + 2] =  tcu::Vec4(2.0f / float(GRID_SIZE),	2.0f / float(GRID_SIZE),	0.0f, 1.0f);
		positionData[primNdx*6 + 3] =  tcu::Vec4(0.0f,						0.0f,						0.0f, 1.0f);
		positionData[primNdx*6 + 4] =  tcu::Vec4(2.0f / float(GRID_SIZE),	2.0f / float(GRID_SIZE),	0.0f, 1.0f);
		positionData[primNdx*6 + 5] =  tcu::Vec4(2.0f / float(GRID_SIZE),	0.0f,						0.0f, 1.0f);
	}

	// color & offset

	for (int y = 0; y < GRID_SIZE; ++y)
	for (int x = 0; x < GRID_SIZE; ++x)
	{
		for (int v = 0; v < numInstanceElementsPerCell; ++v)
		{
			const tcu::Vec4& color = ((x + y) % 2 == 0) ? (green) : (yellow);

			colorOffsetData[((y * GRID_SIZE + x) * numInstanceElementsPerCell + v) * 2 + 0] = color;
			colorOffsetData[((y * GRID_SIZE + x) * numInstanceElementsPerCell + v) * 2 + 1] = tcu::Vec4(float(x) / float(GRID_SIZE) * 2.0f - 1.0f, float(y) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 0.0f);
		}
	}

	// upload vertex data

	gl.genBuffers(1, &m_posBuffer);
	gl.bindBuffer(GL_ARRAY_BUFFER, m_posBuffer);
	gl.bufferData(GL_ARRAY_BUFFER, (int)(positionData.size() * sizeof(tcu::Vec4)), positionData[0].getPtr(), GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "upload position data");

	gl.genBuffers(1, &m_colorOffsetBuffer);
	gl.bindBuffer(GL_ARRAY_BUFFER, m_colorOffsetBuffer);
	gl.bufferData(GL_ARRAY_BUFFER, (int)(colorOffsetData.size() * sizeof(tcu::Vec4)), colorOffsetData[0].getPtr(), GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "upload position data");
}

void MixedBindingCase::createShader (void)
{
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_positionColorOffsetShader) << glu::FragmentSource(s_colorFragmentShader));
	m_testCtx.getLog() << *m_program;

	if (!m_program->isOk())
		throw tcu::TestError("could not build shader");
}

class MixedApiCase : public BindingRenderCase
{
public:

	enum CaseType
	{
		CASE_CHANGE_BUFFER = 0,
		CASE_CHANGE_BUFFER_OFFSET,
		CASE_CHANGE_BUFFER_STRIDE,
		CASE_CHANGE_BINDING_POINT,

		CASE_LAST
	};

						MixedApiCase			(Context& ctx, const char* name, const char* desc, CaseType caseType);
						~MixedApiCase			(void);

	void				init					(void);
	void				deinit					(void);

private:
	enum
	{
		GRID_SIZE = 20
	};

	void				renderTo				(tcu::Surface& dst);
	void				createBuffers			(void);
	void				createShader			(void);

	const CaseType		m_case;
	glw::GLuint			m_buffer;
};


MixedApiCase::MixedApiCase (Context& ctx, const char* name, const char* desc, CaseType caseType)
	: BindingRenderCase		(ctx, name, desc, false)
	, m_case				(caseType)
	, m_buffer				(0)
{
	DE_ASSERT(caseType < CASE_LAST);
}

MixedApiCase::~MixedApiCase (void)
{
	deinit();
}

void MixedApiCase::init (void)
{
	BindingRenderCase::init();
}

void MixedApiCase::deinit (void)
{
	if (m_buffer)
	{
		m_context.getRenderContext().getFunctions().deleteBuffers(1, &m_buffer);
		m_buffer = DE_NULL;
	}

	BindingRenderCase::deinit();
}

void MixedApiCase::renderTo (tcu::Surface& dst)
{
	glu::CallLogWrapper		gl				(m_context.getRenderContext().getFunctions(), m_testCtx.getLog());
	const int				positionLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_position");
	const int				colorLoc		= gl.glGetAttribLocation(m_program->getProgram(), "a_color");
	glu::Buffer				dummyBuffer		(m_context.getRenderContext());

	gl.enableLogging(true);

	gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
	gl.glClear(GL_COLOR_BUFFER_BIT);
	gl.glViewport(0, 0, dst.getWidth(), dst.getHeight());
	gl.glBindVertexArray(m_vao);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "set vao");

	gl.glUseProgram(m_program->getProgram());
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "use program");

	switch (m_case)
	{
		case CASE_CHANGE_BUFFER:
		{
			// bind data using old api

			gl.glBindBuffer(GL_ARRAY_BUFFER, *dummyBuffer);
			gl.glVertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL);
			gl.glVertexAttribPointer(colorLoc,    4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), glu::BufferOffsetAsPointer(sizeof(tcu::Vec4)));

			// change buffer with vertex_attrib_binding

			gl.glBindVertexBuffer(positionLoc, m_buffer, 0,                 (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
			gl.glBindVertexBuffer(colorLoc,    m_buffer, sizeof(tcu::Vec4), (glw::GLsizei)(2 * sizeof(tcu::Vec4)));

			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
			break;
		}

		case CASE_CHANGE_BUFFER_OFFSET:
		{
			// bind data using old api

			gl.glBindBuffer(GL_ARRAY_BUFFER, m_buffer);
			gl.glVertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL);
			gl.glVertexAttribPointer(colorLoc,    4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL);

			// change buffer offset with vertex_attrib_binding

			gl.glBindVertexBuffer(positionLoc, m_buffer, 0,                 (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
			gl.glBindVertexBuffer(colorLoc,    m_buffer, sizeof(tcu::Vec4), (glw::GLsizei)(2 * sizeof(tcu::Vec4)));

			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
			break;
		}

		case CASE_CHANGE_BUFFER_STRIDE:
		{
			// bind data using old api

			gl.glBindBuffer(GL_ARRAY_BUFFER, m_buffer);
			gl.glVertexAttribPointer(positionLoc, 4, GL_FLOAT, GL_FALSE, 8, (const deUint8*)DE_NULL);
			gl.glVertexAttribPointer(colorLoc,    4, GL_FLOAT, GL_FALSE, 4, (const deUint8*)DE_NULL);

			// change buffer stride with vertex_attrib_binding

			gl.glBindVertexBuffer(positionLoc, m_buffer, 0,                 (glw::GLsizei)(2 * sizeof(tcu::Vec4)));
			gl.glBindVertexBuffer(colorLoc,    m_buffer, sizeof(tcu::Vec4), (glw::GLsizei)(2 * sizeof(tcu::Vec4)));

			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
			break;
		}

		case CASE_CHANGE_BINDING_POINT:
		{
			const int maxUsedLocation	= de::max(positionLoc, colorLoc);
			const int bindingPoint1		= maxUsedLocation + 1;
			const int bindingPoint2		= maxUsedLocation + 2;

			// bind data using old api

			gl.glBindBuffer(GL_ARRAY_BUFFER, m_buffer);
			gl.glVertexAttribPointer(bindingPoint1, 4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), (const deUint8*)DE_NULL);
			gl.glVertexAttribPointer(bindingPoint2, 4, GL_FLOAT, GL_FALSE, (glw::GLsizei)(2 * sizeof(tcu::Vec4)), glu::BufferOffsetAsPointer(sizeof(tcu::Vec4)));

			// change buffer binding point with vertex_attrib_binding

			gl.glVertexAttribFormat(positionLoc, 4, GL_FLOAT, GL_FALSE, 0);
			gl.glVertexAttribFormat(colorLoc, 4, GL_FLOAT, GL_FALSE, 0);

			gl.glVertexAttribBinding(positionLoc, bindingPoint1);
			gl.glVertexAttribBinding(colorLoc, bindingPoint2);

			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");
			break;
		}

		default:
			DE_ASSERT(DE_FALSE);
	}

	// draw
	gl.glEnableVertexAttribArray(positionLoc);
	gl.glEnableVertexAttribArray(colorLoc);
	gl.glDrawArrays(GL_TRIANGLES, 0, 6*GRID_SIZE*GRID_SIZE);

	gl.glFinish();
	gl.glBindVertexArray(0);
	gl.glUseProgram(0);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "clean");

	glu::readPixels(m_context.getRenderContext(), 0, 0, dst.getAccess());
}

void MixedApiCase::createBuffers (void)
{
	const tcu::Vec4			green							= tcu::Vec4(0.0f, 1.0f, 0.0f, 1.0f);
	const tcu::Vec4			yellow							= tcu::Vec4(1.0f, 1.0f, 0.0f, 1.0f);

	const glw::Functions&	gl								= m_context.getRenderContext().getFunctions();
	std::vector<tcu::Vec4>	vertexData						(12 * GRID_SIZE * GRID_SIZE);

	for (int y = 0; y < GRID_SIZE; ++y)
	for (int x = 0; x < GRID_SIZE; ++x)
	{
		const tcu::Vec4& color = ((x + y) % 2 == 0) ? (green) : (yellow);

		vertexData[(y * GRID_SIZE + x) * 12 +  0] = tcu::Vec4(float(x+0) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
		vertexData[(y * GRID_SIZE + x) * 12 +  1] = color;
		vertexData[(y * GRID_SIZE + x) * 12 +  2] = tcu::Vec4(float(x+0) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
		vertexData[(y * GRID_SIZE + x) * 12 +  3] = color;
		vertexData[(y * GRID_SIZE + x) * 12 +  4] = tcu::Vec4(float(x+1) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
		vertexData[(y * GRID_SIZE + x) * 12 +  5] = color;
		vertexData[(y * GRID_SIZE + x) * 12 +  6] = tcu::Vec4(float(x+0) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
		vertexData[(y * GRID_SIZE + x) * 12 +  7] = color;
		vertexData[(y * GRID_SIZE + x) * 12 +  8] = tcu::Vec4(float(x+1) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+1) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
		vertexData[(y * GRID_SIZE + x) * 12 +  9] = color;
		vertexData[(y * GRID_SIZE + x) * 12 + 10] = tcu::Vec4(float(x+1) / float(GRID_SIZE) * 2.0f - 1.0f, float(y+0) / float(GRID_SIZE) * 2.0f - 1.0f, 0.0f, 1.0f);
		vertexData[(y * GRID_SIZE + x) * 12 + 11] = color;
	}

	// upload vertex data

	gl.genBuffers(1, &m_buffer);
	gl.bindBuffer(GL_ARRAY_BUFFER, m_buffer);
	gl.bufferData(GL_ARRAY_BUFFER, (int)(vertexData.size() * sizeof(tcu::Vec4)), vertexData[0].getPtr(), GL_STATIC_DRAW);
	GLU_EXPECT_NO_ERROR(gl.getError(), "upload data");
}

void MixedApiCase::createShader (void)
{
	m_program = new glu::ShaderProgram(m_context.getRenderContext(), glu::ProgramSources() << glu::VertexSource(s_positionColorShader) << glu::FragmentSource(s_colorFragmentShader));
	m_testCtx.getLog() << *m_program;

	if (!m_program->isOk())
		throw tcu::TestError("could not build shader");
}

class DefaultVAOCase : public TestCase
{
public:
	enum CaseType
	{
		CASE_BIND_VERTEX_BUFFER,
		CASE_VERTEX_ATTRIB_FORMAT,
		CASE_VERTEX_ATTRIB_I_FORMAT,
		CASE_VERTEX_ATTRIB_BINDING,
		CASE_VERTEX_BINDING_DIVISOR,

		CASE_LAST
	};

					DefaultVAOCase		(Context& ctx, const char* name, const char* desc, CaseType caseType);
					~DefaultVAOCase		(void);

	IterateResult	iterate				(void);

private:
	const CaseType	m_caseType;
};

DefaultVAOCase::DefaultVAOCase (Context& ctx, const char* name, const char* desc, CaseType caseType)
	: TestCase		(ctx, name, desc)
	, m_caseType	(caseType)
{
	DE_ASSERT(caseType < CASE_LAST);
}

DefaultVAOCase::~DefaultVAOCase (void)
{
}

DefaultVAOCase::IterateResult DefaultVAOCase::iterate (void)
{
	glw::GLenum			error	= 0;
	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());

	gl.enableLogging(true);

	switch (m_caseType)
	{
		case CASE_BIND_VERTEX_BUFFER:
		{
			glu::Buffer buffer(m_context.getRenderContext());
			gl.glBindVertexBuffer(0, *buffer, 0, 0);
			break;
		}

		case CASE_VERTEX_ATTRIB_FORMAT:
			gl.glVertexAttribFormat(0, 4, GL_FLOAT, GL_FALSE, 0);
			break;

		case CASE_VERTEX_ATTRIB_I_FORMAT:
			gl.glVertexAttribIFormat(0, 4, GL_INT, 0);
			break;

		case CASE_VERTEX_ATTRIB_BINDING:
			gl.glVertexAttribBinding(0, 0);
			break;

		case CASE_VERTEX_BINDING_DIVISOR:
			gl.glVertexBindingDivisor(0, 1);
			break;

		default:
			DE_ASSERT(false);
	}

	error = gl.glGetError();

	if (error != GL_INVALID_OPERATION)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_INVALID_OPERATION, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid error");
	}
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	return STOP;
}

class BindToCreateCase : public TestCase
{
public:
					BindToCreateCase	(Context& ctx, const char* name, const char* desc);
					~BindToCreateCase	(void);

	IterateResult	iterate				(void);
};

BindToCreateCase::BindToCreateCase (Context& ctx, const char* name, const char* desc)
	: TestCase(ctx, name, desc)
{
}

BindToCreateCase::~BindToCreateCase (void)
{
}

BindToCreateCase::IterateResult BindToCreateCase::iterate (void)
{
	glw::GLuint			buffer	= 0;
	glw::GLenum			error;
	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
	glu::VertexArray	vao		(m_context.getRenderContext());

	gl.enableLogging(true);

	gl.glGenBuffers(1, &buffer);
	gl.glDeleteBuffers(1, &buffer);
	GLU_EXPECT_NO_ERROR(gl.glGetError(), "");

	gl.glBindVertexArray(*vao);
	gl.glBindVertexBuffer(0, buffer, 0, 0);

	error = gl.glGetError();

	if (error != GL_INVALID_OPERATION)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_INVALID_OPERATION, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid error");
	}
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	return STOP;
}

class NegativeApiCase : public TestCase
{
public:
	enum CaseType
	{
		CASE_LARGE_OFFSET,
		CASE_LARGE_STRIDE,
		CASE_NEGATIVE_STRIDE,
		CASE_NEGATIVE_OFFSET,
		CASE_INVALID_ATTR,
		CASE_INVALID_BINDING,

		CASE_LAST
	};
					NegativeApiCase		(Context& ctx, const char* name, const char* desc, CaseType caseType);
					~NegativeApiCase	(void);

	IterateResult	iterate				(void);

private:
	const CaseType	m_caseType;
};

NegativeApiCase::NegativeApiCase (Context& ctx, const char* name, const char* desc, CaseType caseType)
	: TestCase		(ctx, name, desc)
	, m_caseType	(caseType)
{
}

NegativeApiCase::~NegativeApiCase (void)
{
}

NegativeApiCase::IterateResult NegativeApiCase::iterate (void)
{
	glw::GLenum			error;
	glu::CallLogWrapper gl		(m_context.getRenderContext().getFunctions(), m_context.getTestContext().getLog());
	glu::VertexArray	vao		(m_context.getRenderContext());

	gl.enableLogging(true);
	gl.glBindVertexArray(*vao);

	switch (m_caseType)
	{
		case CASE_LARGE_OFFSET:
		{
			glw::GLint	maxOffset	= -1;
			glw::GLint	largeOffset;

			gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET, &maxOffset);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");

			largeOffset = maxOffset + 1;

			// skip if maximum unsigned or signed values
			if (maxOffset == -1 || maxOffset == 0x7FFFFFFF)
				throw tcu::NotSupportedError("Implementation supports all offsets");

			gl.glVertexAttribFormat(0, 4, GL_FLOAT, GL_FALSE, largeOffset);
			break;
		}

		case CASE_LARGE_STRIDE:
		{
			glu::Buffer buffer		(m_context.getRenderContext());
			glw::GLint	maxStride	= -1;
			glw::GLint	largeStride;

			gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_STRIDE, &maxStride);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");

			largeStride = maxStride + 1;

			// skip if maximum unsigned or signed values
			if (maxStride == -1 || maxStride == 0x7FFFFFFF)
				throw tcu::NotSupportedError("Implementation supports all strides");

			gl.glBindVertexBuffer(0, *buffer, 0, largeStride);
			break;
		}

		case CASE_NEGATIVE_STRIDE:
		{
			glu::Buffer buffer(m_context.getRenderContext());
			gl.glBindVertexBuffer(0, *buffer, 0, -20);
			break;
		}

		case CASE_NEGATIVE_OFFSET:
		{
			glu::Buffer buffer(m_context.getRenderContext());
			gl.glBindVertexBuffer(0, *buffer, -20, 0);
			break;
		}

		case CASE_INVALID_ATTR:
		{
			glw::GLint maxIndex = -1;
			glw::GLint largeIndex;

			gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxIndex);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");

			largeIndex = maxIndex + 1;

			// skip if maximum unsigned or signed values
			if (maxIndex == -1 || maxIndex == 0x7FFFFFFF)
				throw tcu::NotSupportedError("Implementation supports any attribute index");

			gl.glVertexAttribBinding(largeIndex, 0);
			break;
		}

		case CASE_INVALID_BINDING:
		{
			glw::GLint maxBindings = -1;
			glw::GLint largeBinding;

			gl.glGetIntegerv(GL_MAX_VERTEX_ATTRIB_BINDINGS, &maxBindings);
			GLU_EXPECT_NO_ERROR(gl.glGetError(), "");

			largeBinding = maxBindings + 1;

			// skip if maximum unsigned or signed values
			if (maxBindings == -1 || maxBindings == 0x7FFFFFFF)
				throw tcu::NotSupportedError("Implementation supports any binding");

			gl.glVertexAttribBinding(0, largeBinding);
			break;
		}

		default:
			DE_ASSERT(false);
	}

	error = gl.glGetError();

	if (error != GL_INVALID_VALUE)
	{
		m_testCtx.getLog() << tcu::TestLog::Message << "ERROR! Expected GL_INVALID_VALUE, got " << glu::getErrorStr(error) << tcu::TestLog::EndMessage;
		m_testCtx.setTestResult(QP_TEST_RESULT_FAIL, "Invalid error");
	}
	else
		m_testCtx.setTestResult(QP_TEST_RESULT_PASS, "Pass");

	return STOP;
}

} // anonymous

VertexAttributeBindingTests::VertexAttributeBindingTests (Context& context)
	: TestCaseGroup(context, "vertex_attribute_binding", "Test vertex attribute binding")
{
}

VertexAttributeBindingTests::~VertexAttributeBindingTests (void)
{
}

void VertexAttributeBindingTests::init (void)
{
	tcu::TestCaseGroup* const usageGroup	= new tcu::TestCaseGroup(m_testCtx, "usage", "Test using binding points");
	tcu::TestCaseGroup* const negativeGroup	= new tcu::TestCaseGroup(m_testCtx, "negative", "Negative test");

	addChild(usageGroup);
	addChild(negativeGroup);

	// .usage
	{
		tcu::TestCaseGroup* const singleGroup	= new tcu::TestCaseGroup(m_testCtx, "single_binding", "Test using single binding point");
		tcu::TestCaseGroup* const multipleGroup	= new tcu::TestCaseGroup(m_testCtx, "multiple_bindings", "Test using multiple binding points");
		tcu::TestCaseGroup* const mixedGroup	= new tcu::TestCaseGroup(m_testCtx, "mixed_usage", "Test using binding point and non binding point api variants");

		usageGroup->addChild(singleGroup);
		usageGroup->addChild(multipleGroup);
		usageGroup->addChild(mixedGroup);

		// single binding

		singleGroup->addChild(new SingleBindingCase(m_context, "elements_1",																					  0));
		singleGroup->addChild(new SingleBindingCase(m_context, "elements_2",																					  SingleBindingCase::FLAG_ATTRIBS_MULTIPLE_ELEMS));
		singleGroup->addChild(new SingleBindingCase(m_context, "elements_2_share_elements",																		  SingleBindingCase::FLAG_ATTRIBS_SHARED_ELEMS));
		singleGroup->addChild(new SingleBindingCase(m_context, "offset_elements_1",								SingleBindingCase::FLAG_BUF_ALIGNED_OFFSET		| 0));
		singleGroup->addChild(new SingleBindingCase(m_context, "offset_elements_2",								SingleBindingCase::FLAG_BUF_ALIGNED_OFFSET		| SingleBindingCase::FLAG_ATTRIBS_MULTIPLE_ELEMS));
		singleGroup->addChild(new SingleBindingCase(m_context, "offset_elements_2_share_elements",				SingleBindingCase::FLAG_BUF_ALIGNED_OFFSET		| SingleBindingCase::FLAG_ATTRIBS_SHARED_ELEMS));
		singleGroup->addChild(new SingleBindingCase(m_context, "unaligned_offset_elements_1_aligned_elements",	SingleBindingCase::FLAG_BUF_UNALIGNED_OFFSET	| SingleBindingCase::FLAG_ATTRIB_ALIGNED));			// !< total offset is aligned

		// multiple bindings

		multipleGroup->addChild(new MultipleBindingCase(m_context, "basic",									0));
		multipleGroup->addChild(new MultipleBindingCase(m_context, "zero_stride",							MultipleBindingCase::FLAG_ZERO_STRIDE));
		multipleGroup->addChild(new MultipleBindingCase(m_context, "instanced",								MultipleBindingCase::FLAG_INSTANCED));
		multipleGroup->addChild(new MultipleBindingCase(m_context, "aliasing_buffer_zero_stride",			MultipleBindingCase::FLAG_ALIASING_BUFFERS	| MultipleBindingCase::FLAG_ZERO_STRIDE));
		multipleGroup->addChild(new MultipleBindingCase(m_context, "aliasing_buffer_instanced",				MultipleBindingCase::FLAG_ALIASING_BUFFERS	| MultipleBindingCase::FLAG_INSTANCED));

		// mixed cases
		mixedGroup->addChild(new MixedBindingCase(m_context,		"mixed_attribs_basic",					"Use different api for different attributes",			MixedBindingCase::CASE_BASIC));
		mixedGroup->addChild(new MixedBindingCase(m_context,		"mixed_attribs_instanced_binding",		"Use different api for different attributes",			MixedBindingCase::CASE_INSTANCED_BINDING));
		mixedGroup->addChild(new MixedBindingCase(m_context,		"mixed_attribs_instanced_attrib",		"Use different api for different attributes",			MixedBindingCase::CASE_INSTANCED_ATTRIB));

		mixedGroup->addChild(new MixedApiCase(m_context,			"mixed_api_change_buffer",				"change buffer with vertex_attrib_binding api",			MixedApiCase::CASE_CHANGE_BUFFER));
		mixedGroup->addChild(new MixedApiCase(m_context,			"mixed_api_change_buffer_offset",		"change buffer offset with vertex_attrib_binding api",	MixedApiCase::CASE_CHANGE_BUFFER_OFFSET));
		mixedGroup->addChild(new MixedApiCase(m_context,			"mixed_api_change_buffer_stride",		"change buffer stride with vertex_attrib_binding api",	MixedApiCase::CASE_CHANGE_BUFFER_STRIDE));
		mixedGroup->addChild(new MixedApiCase(m_context,			"mixed_api_change_binding_point",		"change binding point with vertex_attrib_binding api",	MixedApiCase::CASE_CHANGE_BINDING_POINT));
	}

	// negative
	{
		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_bind_vertex_buffer",			"use with default vao",	DefaultVAOCase::CASE_BIND_VERTEX_BUFFER));
		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_vertex_attrib_format",			"use with default vao",	DefaultVAOCase::CASE_VERTEX_ATTRIB_FORMAT));
		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_vertex_attrib_i_format",		"use with default vao",	DefaultVAOCase::CASE_VERTEX_ATTRIB_I_FORMAT));
		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_vertex_attrib_binding",		"use with default vao",	DefaultVAOCase::CASE_VERTEX_ATTRIB_BINDING));
		negativeGroup->addChild(new DefaultVAOCase(m_context,	"default_vao_vertex_binding_divisor",		"use with default vao",	DefaultVAOCase::CASE_VERTEX_BINDING_DIVISOR));

		negativeGroup->addChild(new BindToCreateCase(m_context,	"bind_create_new_buffer",					"bind not existing buffer"));

		negativeGroup->addChild(new NegativeApiCase(m_context, "vertex_attrib_format_large_offset",			"large relative offset",	NegativeApiCase::CASE_LARGE_OFFSET));
		negativeGroup->addChild(new NegativeApiCase(m_context, "bind_vertex_buffer_large_stride",			"large stride",				NegativeApiCase::CASE_LARGE_STRIDE));
		negativeGroup->addChild(new NegativeApiCase(m_context, "bind_vertex_buffer_negative_stride",		"negative stride",			NegativeApiCase::CASE_NEGATIVE_STRIDE));
		negativeGroup->addChild(new NegativeApiCase(m_context, "bind_vertex_buffer_negative_offset",		"negative offset",			NegativeApiCase::CASE_NEGATIVE_OFFSET));
		negativeGroup->addChild(new NegativeApiCase(m_context, "vertex_attrib_binding_invalid_attr",		"bind invalid attr",		NegativeApiCase::CASE_INVALID_ATTR));
		negativeGroup->addChild(new NegativeApiCase(m_context, "vertex_attrib_binding_invalid_binding",		"bind invalid binding",		NegativeApiCase::CASE_INVALID_BINDING));
	}
}

} // Functional
} // gles31
} // deqp
